package solutions.ferpa.ztest.rest;

import br.com.telescope.adsrt.lib.AdsException;
import br.com.telescope.adsrt.lib.BaseRuntime;
import com.google.gson.Gson;
import solutions.ferpa.ztest.rest.action.RestAction;
import solutions.ferpa.ztest.rest.model.res.RestRS;
import solutions.ferpa.ztest.util.ClassUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

public abstract class RestService<RQ, RS extends RestRS> implements Callable<RS> {

    private static class RestActionClass {
        private RestAction annotation;
        private Class<?> serviceClass;

        public RestAction getAnnotation() {
            return annotation;
        }

        public void setAnnotation(RestAction annotation) {
            this.annotation = annotation;
        }

        public Class<?> getServiceClass() {
            return serviceClass;
        }

        public void setServiceClass(Class<?> serviceClass) {
            this.serviceClass = serviceClass;
        }
    }

    private static Map<String, RestActionClass> restActions;

    private static Map<String, RestActionClass> getRestActions() {
        if (restActions == null) {
            restActions = new HashMap<String, RestActionClass>();

            for (Class<?> clazz : ClassUtils.getClassesForPackage(RestAction.class.getClassLoader(), RestAction.class.getPackage())) {
                if (clazz.isAnnotationPresent(RestAction.class)) {
                    for (Annotation annotation : clazz.getDeclaredAnnotations()) {
                        if (annotation.annotationType().equals(RestAction.class)) {
                            RestActionClass restActionClass = new RestActionClass();
                            restActionClass.setAnnotation((RestAction) annotation);
                            restActionClass.setServiceClass(clazz);
                            restActions.put(restActionClass.getAnnotation().action(), restActionClass);
                            break;
                        }
                    }
                }
            }
        }

        return restActions;
    }

    private final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
    private String jsonRequest;
    private boolean restricted;
    protected BaseRuntime ctx;
    protected Gson gson = new Gson();

    public RestService(BaseRuntime ctx) {
        this.ctx = ctx;
    }

    public static RestService createService(BaseRuntime ctx, String action) throws InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        RestService service = null;
        RestActionClass restActionClass = getRestActions().get(action);

        if (restActionClass != null) {
            Constructor constructor = restActionClass.getServiceClass().getConstructors()[0];
            service = (RestService) constructor.newInstance(ctx);
            service.restricted = restActionClass.getAnnotation().restricted();
        }

        return service;
    }

    public String getJsonRequest() {
        return jsonRequest;
    }

    public void setJsonRequest(String jsonRequest) {
        this.jsonRequest = jsonRequest;
    }

    protected RQ bindRequest(Class<RQ> type) {
        RQ rq = null;

        //lro
        System.out.println("JSON:" + getJsonRequest());

        if (getJsonRequest() != null) {
            rq = gson.fromJson(getJsonRequest(), type);
        }

        return rq;
    }

    protected final String message(String key, String defaultMessage, Object... arguments) {
        return ctx.getTranslator().message(key, defaultMessage, arguments);
    }

    public boolean isRestricted() {
        return restricted;
    }

    public RS run() throws Exception {
        return call();
    }

    public Future<RS> submit() {
        return EXECUTOR.submit(this);
    }

    protected final ResultSet executeQuery(String sql) throws AdsException, SQLException {
        Connection conn = ctx.getConnection();
        Statement stm = conn.createStatement();
        return stm.executeQuery(sql);
    }

}